﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace Gmantis.Controls
{
    internal class RelativePopup
    {
        // Fields
        private bool _closeWhenRelativeNotVisible;
        internal bool _dontConstrain;
        private Func<List<AnchorMatching>> _getLocations;
        private Func<Point> _getPosition;
        private Func<Point> _getReferencePosition;
        private bool _hasPopupParent;
        private bool _ignoreFlowDirection;
        private double _overlapHorizontal;
        private double _overlapVertical;
        private Popup _popup;
        private FrameworkElement _relativeTo;
        private FrameworkElement _root;

        // Methods
        public RelativePopup(FrameworkElement relativeTo, Func<List<AnchorMatching>> getLocations, Popup popup)
            : this(relativeTo, getLocations, null, null, popup, 0.0, 0.0, false, true)
        {
        }

        public RelativePopup(FrameworkElement relativeTo, Func<List<AnchorMatching>> getLocations, Popup popup, bool ignoreFlowDirection)
            : this(relativeTo, getLocations, null, null, popup, 0.0, 0.0, false, ignoreFlowDirection)
        {
        }

        public RelativePopup(FrameworkElement relativeTo, Func<List<AnchorMatching>> getLocations, Func<Point> getPosition, Func<Point> getReferencePosition, Popup popup)
            : this(relativeTo, getLocations, getPosition, getReferencePosition, popup, 0.0, 0.0, false, true)
        {
        }

        public RelativePopup(FrameworkElement relativeTo, Func<List<AnchorMatching>> getLocations, Func<Point> getPosition, Func<Point> getReferencePosition, Popup popup, bool ignoreFlowDirection)
            : this(relativeTo, getLocations, getPosition, getReferencePosition, popup, 0.0, 0.0, false, ignoreFlowDirection)
        {
        }

        public RelativePopup(FrameworkElement relativeTo, Func<List<AnchorMatching>> getLocations, Func<Point> getPosition, Func<Point> getReferencePosition, Popup popup, double overlapHorizontal, double overlapVertical, bool autoClose, bool ignoreFlowDirection)
        {
            EventHandler handler = null;
            EventHandler handler2 = null;
            this._closeWhenRelativeNotVisible = true;
            if (relativeTo == null)
            {
                throw new ArgumentNullException("relativeTo");
            }
            if (popup == null)
            {
                throw new ArgumentNullException("child/popup");
            }
            if (getLocations == null)
            {
                throw new ArgumentNullException("getLocations", "Should provide a function to get the locations to try.");
            }
            this._popup = popup;
            this._root = (FrameworkElement)relativeTo.GetRootVisual();
            this._relativeTo = relativeTo;
            this._relativeTo.SizeChanged += new SizeChangedEventHandler(this.UpdateLocationHandler);
            this._getLocations = getLocations;
            this._getPosition = getPosition ?? new Func<Point>(this.GetPosition);
            this._getReferencePosition = getReferencePosition ?? new Func<Point>(this.GetReferencePosition);
            this._ignoreFlowDirection = ignoreFlowDirection;
            this._overlapHorizontal = overlapHorizontal;
            this._overlapVertical = overlapVertical;
            if (handler == null)
            {
                handler = delegate(object s, EventArgs a)
                {
                    FrameworkElement element1 = this._root;
                    this._root = (FrameworkElement)relativeTo.GetRootVisual();
                    this.AttachEvents();
                    this.UpdateLocation(false);
                };
            }
            this._popup.Opened += handler;
            if (handler2 == null)
            {
                handler2 = (s, a) => this.DetachEvents();
            }
            this._popup.Closed += handler2;
            this._closeWhenRelativeNotVisible = autoClose;
            this.ConstrainPopupChild();
        }

        private void AttachEvents()
        {
            this._root.SizeChanged += new SizeChangedEventHandler(this.UpdateLocationHandler);
            FrameworkElement child = this._popup.Child as FrameworkElement;
            if (child != null)
            {
                child.SizeChanged += new SizeChangedEventHandler(this.UpdateLocationHandler);
            }
        }

        private void ConstrainPopupChild()
        {
            FrameworkElement child = this._popup.Child as FrameworkElement;
            if (child != null)
            {
                if (this._dontConstrain)
                {
                    child.ClearValue(FrameworkElement.MaxWidthProperty);
                    child.ClearValue(FrameworkElement.MaxHeightProperty);
                }
                else
                {
                    List<AnchorMatching> list = this._getLocations();
                    if (list.Count == 0)
                    {
                        throw new InvalidOperationException(string.Format("Function {0} should provide at least one preferred location", this._getLocations.ToString()));
                    }
                    Point point = this._relativeTo.GTransformToVisual(null).Transform(new Point(0.0, 0.0));
                    double num = 0.0;
                    double num2 = 0.0;
                    VerticalAlignment top = VerticalAlignment.Top;
                    foreach (AnchorMatching matching in list)
                    {
                        double x = point.X;
                        if (matching.FixedElementAnchor == Anchor.Center)
                        {
                            x += this._relativeTo.ActualWidth * 0.5;
                        }
                        else if ((matching.FixedElementAnchor == Anchor.TopRight) || (matching.FixedElementAnchor == Anchor.BottomRight))
                        {
                            x += this._relativeTo.ActualWidth;
                        }
                        double num4 = x;
                        if (matching.MoveableElementAnchor == Anchor.Center)
                        {
                            num4 = 2.0 * Math.Min(x, GetWindowSizeElement(this._root).ActualWidth - x);
                        }
                        else if ((matching.MoveableElementAnchor == Anchor.BottomLeft) || (matching.MoveableElementAnchor == Anchor.TopLeft))
                        {
                            num4 = GetWindowSizeElement(this._root).ActualWidth - x;
                        }
                        num = Math.Max(num, num4);
                        double y = point.Y;
                        if (matching.FixedElementAnchor == Anchor.Center)
                        {
                            y += this._relativeTo.ActualHeight * 0.5;
                        }
                        else if ((matching.FixedElementAnchor == Anchor.BottomLeft) || (matching.FixedElementAnchor == Anchor.BottomRight))
                        {
                            y += this._relativeTo.ActualHeight;
                        }
                        VerticalAlignment bottom = VerticalAlignment.Bottom;
                        double num6 = y;
                        if (matching.MoveableElementAnchor == Anchor.Center)
                        {
                            num6 = 2.0 * Math.Min(y, GetWindowSizeElement(this._root).ActualHeight - y);
                        }
                        else if ((matching.MoveableElementAnchor == Anchor.TopLeft) || (matching.MoveableElementAnchor == Anchor.TopRight))
                        {
                            num6 = GetWindowSizeElement(this._root).ActualHeight - y;
                            bottom = VerticalAlignment.Top;
                        }
                        if (num2 < num6)
                        {
                            num2 = num6;
                            top = bottom;
                        }
                    }
                    child.MaxWidth = num;
                    child.MaxHeight = num2;
                    RelativePopupPresenter presenter = child as RelativePopupPresenter;
                    if (presenter != null)
                    {
                        FrameworkElement content = presenter.Content as FrameworkElement;
                        if (content != null)
                        {
                            content.VerticalAlignment = top;
                        }
                    }
                }
            }
        }

        private void DetachEvents()
        {
            this._root.SizeChanged -= new SizeChangedEventHandler(this.UpdateLocationHandler);
            FrameworkElement child = this._popup.Child as FrameworkElement;
            if (child != null)
            {
                child.SizeChanged -= new SizeChangedEventHandler(this.UpdateLocationHandler);
            }
        }

        private Point GetPosition()
        {
            return PopupUtil.GetPosition(this._popup, new Point());
        }

        private Point GetReferencePosition()
        {
            return PopupUtil.GetPosition(this._relativeTo, new Point());
        }

        internal static FrameworkElement GetWindowSizeElement(FrameworkElement el)
        {
            return el;
        }

        private bool IsOnScreen(FrameworkElement element)
        {
            Rect accumulatedClip = element.GetAccumulatedClip(null);
            return ((((accumulatedClip.Top <= 0.0) && (accumulatedClip.Left <= 0.0)) && (accumulatedClip.Bottom >= element.ActualHeight)) && (accumulatedClip.Right >= element.ActualWidth));
        }

        private bool OutOfBorders(Point location, Size size, Size borderSize, FlowDirection flowDir)
        {
            if (flowDir == FlowDirection.LeftToRight)
            {
                if (((location.X >= 0.0) && (location.Y >= 0.0)) && ((location.X + size.Width) <= borderSize.Width))
                {
                    return ((location.Y + size.Height) > borderSize.Height);
                }
                return true;
            }
            if ((((location.X - size.Width) >= 0.0) && (location.Y >= 0.0)) && (location.X <= borderSize.Width))
            {
                return ((location.Y + size.Height) > borderSize.Height);
            }
            return true;
        }

        internal void UpdateLocation(bool force = false)
        {
            this.ConstrainPopupChild();
            if ((force || this._popup.IsOpen) && (this._popup.Child is FrameworkElement))
            {
                FrameworkElement child = this._popup.Child as FrameworkElement;
                FlowDirection flowDirection = this._popup.FlowDirection;
                FlowDirection flowDir = this._ignoreFlowDirection ? FlowDirection.LeftToRight : flowDirection;
                this._popup.RenderTransform = null;
                this._popup.HorizontalOffset = 0.0;
                this._popup.VerticalOffset = 0.0;
                Size desiredSize = child.DesiredSize;
                Size borderSize = GetWindowSizeElement(this._root).ActualSize();
                if (!this.IsOnScreen(this._relativeTo) && this._closeWhenRelativeNotVisible)
                {
                    this._popup.IsOpen = false;
                }
                else
                {
                    List<AnchorMatching> list = this._getLocations();
                    if (list.Count == 0)
                    {
                        throw new InvalidOperationException(string.Format("Function {0} should provide at least one preferred location", this._getLocations.ToString()));
                    }
                    Point location = new Point();
                    int num = 0;
                    bool flag = false;
                    do
                    {
                        AnchorMatching matching = list[num];
                        location = PopupUtil.TestPosition(child, matching.MoveableElementAnchor, this._relativeTo, matching.FixedElementAnchor, this._getReferencePosition(), this._overlapHorizontal, this._overlapVertical, flowDir);
                        flag = this.OutOfBorders(location, desiredSize, borderSize, flowDir);
                        num++;
                    }
                    while (flag && (num < list.Count));
                    Point point2 = this._getPosition();
                    if (flowDir == FlowDirection.RightToLeft)
                    {
                        point2.X *= -1.0;
                        location.X *= -1.0;
                    }
                    TranslateTransform transform = new TranslateTransform
                    {
                        X = -point2.X,
                        Y = -point2.Y
                    };
                    this._popup.RenderTransform = transform;
                    if (!this._hasPopupParent || !(this._relativeTo is DropDown))
                    {
                        this._popup.HorizontalOffset = location.X;
                        this._popup.VerticalOffset = location.Y;
                    }
                    else
                    {
                        switch ((this._relativeTo as DropDownButton).DropDownDirection)
                        {
                            case DropDownDirection.BelowOrAbove:
                            case DropDownDirection.ForceBelow:
                                this._popup.VerticalOffset = this._relativeTo.ActualHeight;
                                break;

                            case DropDownDirection.AboveOrBelow:
                            case DropDownDirection.ForceAbove:
                                this._popup.VerticalOffset = -child.ActualHeight;
                                return;

                            default:
                                return;
                        }
                    }
                }
            }
        }

        private void UpdateLocationHandler(object sender, EventArgs e)
        {
            this.UpdateLocation(false);
        }
    }
}
